package com.godfeer.rest.common.aop;

import com.alibaba.fastjson.JSON;
import com.godfeer.rest.common.exception.ExceptionHandle;
import com.godfeer.rest.common.exception.ip.RequestLimit;
import com.godfeer.rest.common.exception.ip.RequestLimitException;
import com.google.common.collect.Maps;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.apache.log4j.Logger;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * 全局的的异常拦截器（拦截所有的控制器）（带有@RequestMapping注解的方法上都会拦截）
 *
 * @author godfeer
 * @date 2016年11月12日 下午3:19:56
 */
@Aspect
@Component
public class ExceptionAspectComponent {

    private final static Logger logger = Logger.getLogger(ExceptionAspectComponent.class);

    @Autowired
    private ExceptionHandle exceptionHandle;

    @Pointcut("execution(public * com.godfeer.rest.modular..*.*(..))")
    public void executeService(){

    }
//    /**
//     * 环绕通知：
//     *   环绕通知非常强大，可以决定目标方法是否执行，什么时候执行，执行时是否需要替换方法参数，执行完毕是否需要替换返回值。
//     *   环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
//     */
//    @Around("execution(* com.godfeer.admin.rest.modular..*.*(..))")
//    public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
//        logger.info("***********请求信息START***********");
//        logger.info("请求方法名称："+proceedingJoinPoint.getSignature().getName());
//        Object obj = "";
//        try {
//             obj = proceedingJoinPoint.proceed();
//            return JSONObject.toJSON(ResultUtil.success(obj));
//        } catch (Throwable throwable) {
//            throwable.printStackTrace();
//        }
//        return obj;
//    }

    private Map<String , Integer> redisTemplate = new HashMap<>();
    /**
     * 前置通知，方法调用前被调用
     * @param joinPoint
     */
    @Before("executeService() && @annotation(limit)")
    public void doBeforeAdvice(JoinPoint joinPoint, RequestLimit limit) throws RequestLimitException {
        logger.info("***********请求信息START***********");
        //获取目标方法的参数信息
        Object[] obj = joinPoint.getArgs();
        //AOP代理类的信息
        joinPoint.getThis();
        //代理的目标对象
        joinPoint.getTarget();
        //用的最多 通知的签名
        Signature signature = joinPoint.getSignature();
        //代理的是哪一个方法
        logger.info("**请求方法：/"+signature.getName());
        //AOP代理类的名字
        logger.info("**代理类的名字："+signature.getDeclaringTypeName());
        //AOP代理类的类（class）信息
        signature.getDeclaringType();
        //获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        //如果要获取Session信息的话，可以这样写：
        //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
        Enumeration<String> enumeration = request.getParameterNames();
        Map<String,String> parameterMap = Maps.newHashMap();
        while (enumeration.hasMoreElements()){
            String parameter = enumeration.nextElement();
            parameterMap.put(parameter,request.getParameter(parameter));
        }
        String str = JSON.toJSONString(parameterMap);
        if(obj.length > 0) {
            logger.info("**请求的参数信息为："+str);
        }
        try {
            Object[] args = joinPoint.getArgs();

            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof HttpServletRequest) {
                    request = (HttpServletRequest) args[i];
                    break;
                }
            }
            if (request == null) {
                throw new RequestLimitException("方法中缺失HttpServletRequest参数");
            }
            String ip = request.getLocalAddr();
            String url = request.getRequestURL().toString();
            String key = "req_limit_".concat(url).concat(ip);
            if (redisTemplate.get(key) == null || redisTemplate.get(key) == 0) {
                redisTemplate.put(key, 1);
            } else {
                redisTemplate.put(key, redisTemplate.get(key) + 1);
            }
            int count = redisTemplate.get(key);

            if (count > 0) {
                //创建一个定时器
                Timer timer = new Timer();
                TimerTask timerTask = new TimerTask() {
                    @Override
                    public void run() {
                        redisTemplate.remove(key);
                    }
                };
                //这个定时器设定在time规定的时间之后会执行上面的remove方法，也就是说在这个时间后它可以重新访问
                timer.schedule(timerTask, limit.time());
            }
            if (count > limit.count()) {
                logger.info("用户IP[" + ip + "]访问地址[" + url + "]超过了限定的次数[" + limit.count() + "]");
                throw new RequestLimitException("接口访问次数超限，请等待[" + limit.time() + "]秒");
            }
        }catch (RequestLimitException e){
            throw e;
        }catch (Exception e){
            logger.error("发生异常",e);
        }
    }

    /**
     * 后置返回通知
     * 这里需要注意的是:
     *      如果参数中的第一个参数为JoinPoint，则第二个参数为返回值的信息
     *      如果参数中的第一个参数不为JoinPoint，则第一个参数为returning中对应的参数
     * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知，否则不执行，对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
     * @param joinPoint
     * @param keys
     */
//    @AfterReturning(value = "execution(* com.godfeer.admin.rest.modular..*.*(..)))",returning = "keys")
//    public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){
//
//        logger.info("第一个后置返回通知的返回值："+keys);
//
//    }
//
//    @AfterReturning(value = "execution(* com.godfeer.admin.rest.modular..*.*(..)))",returning = "keys",argNames = "keys")
//    public void doAfterReturningAdvice2(String keys){
//
//        logger.info("**第二个后置返回通知的返回值："+keys);
//    }

    /**
     * 后置异常通知
     *  定义一个名字，该名字用于匹配通知实现方法的一个参数名，当目标方法抛出异常返回后，将把目标方法抛出的异常传给通知方法；
     *  throwing 限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
     *      对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "executeService()",throwing = "exception")
    public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception){
        //目标方法名：
        logger.info(joinPoint.getSignature().getName());
        if(exception instanceof NullPointerException){
            logger.info("**发生了空指针异常!!!!!"+joinPoint.getSignature().getName());
        }
    }

    /**
     * 后置最终通知（目标方法只要执行完了就会执行后置通知法）
     * @param joinPoint
     */
    @After("executeService()")
    public void doAfterAdvice(JoinPoint joinPoint){
        logger.info("***********请求信息END***********");
    }


}